﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.Diagnostics;

namespace DARE
{
    public class CAnimationController
    {
        #region Fields

        internal readonly CHierarchy m_hierarchy;

        protected readonly int m_nodeCount;
        protected readonly bool m_useSkin;
        protected readonly List<CAnimConfig> m_config;
        protected readonly List<CAnimConfig> m_ending;
        protected readonly List<IBlendController> m_blenders;
        protected readonly Dictionary<string, CAnimConfig> m_animEnded;

        internal readonly int[] m_skinindexer;

        private readonly List<CAttach> m_attachs;

        protected Matrix[] m_bindPose;

        private Matrix[] m_boneTransforms;
        private Matrix[] m_worldTransforms;
        private Matrix[] m_skinTransforms;

        #endregion

        #region Properties

        internal bool AsSkinnedSkeleton { get; set; }

        public List<string> BonesNames
        {
            get { return (from el in m_hierarchy.Nodes select el.Name).ToList(); }
        }

        public Matrix[] BonesRelativeTransform
        {
            get { return this.m_boneTransforms; }
        }

        public Matrix[] WorldTransform
        {
            get { return this.m_worldTransforms; }
        }

        public Matrix[] SkinningTransform
        {
            get { return this.m_skinTransforms; }
        }

        #endregion

        #region CTOR

        public CAnimationController(CAnimNode[] hierarchy) : this(new CHierarchy(hierarchy)) { }

        public CAnimationController(CHierarchy _hierachy)
        {
            m_attachs = new List<CAttach>();

            m_animEnded = new Dictionary<string, CAnimConfig>();
            m_config = new List<CAnimConfig>();
            m_hierarchy = _hierachy;
            m_nodeCount = _hierachy.NodesCount;

            //m_useBones = (m_hierarchy is CSkeleton);

            m_bindPose = new Matrix[m_nodeCount];

            m_boneTransforms = new Matrix[m_nodeCount];
            m_worldTransforms = new Matrix[m_nodeCount];

            m_skinindexer = (from el in _hierachy.Nodes where el.IsBones select el.Index).ToArray(); ;

            m_skinTransforms = new Matrix[m_skinindexer.Length];

            m_blenders = new List<IBlendController>();
            m_ending = new List<CAnimConfig>();

            AsSkinnedSkeleton = false;

            ResetBindPose();
        }

        #endregion

        #region Method

        public CAnimConfig Play(string animName, bool priority, EAnimState state)
        {
            if ((state & EAnimState.RUN) == EAnimState.STOP)
                return null;
            var e = (from el in m_config where el.AnimName == animName select el).SingleOrDefault();
            if (e != null)
                return e;
            CAnimConfig _cac = null;
            if (m_animEnded.ContainsKey(animName))
            {
                _cac = m_animEnded[animName];
                m_animEnded.Remove(animName);
            }
            else
            {
                _cac = CAnimationMgr.GetAnimationByName(animName); //m_useBones
                if (_cac == null)
                    return null;
                var index = m_hierarchy.FindSkeletonBoneIndex(_cac.Animation.Hierarchy);
                if (index.Count != _cac.Animation.Hierarchy.NodesCount)
                    return null;
                _cac.UpdateIndex(index);
            }
            _cac.Reset(state);
            m_config.Add(_cac);
            _cac.Speed = 1.0f;
            _cac.BlendFactor = 1.0f;
            _cac.AnimationEnded += new AnimationEvent<CAnimConfig>(conf_AnimationEnded);
            _cac.Priority = priority;
            return _cac;
        }

        public CAnimConfig this[string animName]
        {
            get
            {
                return (from el in this.m_config where el.AnimName == animName select el).SingleOrDefault();
            }
        }

        public int  GetBoneIndex(string name)
        {
            return this.m_hierarchy.GetIndexByName(name);
        }

        public virtual void Update(GameTime time, Matrix world)
        {
            int animCount = m_config.Count;

            UpdateOverride(world);

            List<KeyValuePair<float, CTransform>> _blend = new List<KeyValuePair<float, CTransform>>();
            bool anim = animCount > 0;
            for (int i = 0; i < animCount; ++i)
            {
                m_config[i].Update(time);
            }
            for (int i = 0; i < m_blenders.Count; ++i)
                m_blenders[i].Update(time);
            for (int i = 0; i < m_nodeCount; ++i)
            {
                float _allblend = 0;
                CTransform _t = new CTransform();

                for (int j = 0; j < animCount; ++j)
                {
                    CAnimConfig _conf = m_config[j];
                    CTransform _trans = _conf[i];
                    if (_trans != null)
                    {
                        if (_conf.Priority)
                        {
                            _blend.Clear();
                            _blend.Add(new KeyValuePair<float, CTransform>(_conf.BlendFactor, _trans));
                            _allblend = _conf.BlendFactor;
                            break;
                        }
                        else
                        {
                            _blend.Add(new KeyValuePair<float, CTransform>(_conf.BlendFactor, _trans));
                            _allblend += _conf.BlendFactor;
                        }
                    }
                }
                if (_allblend == 0.0f)
                    m_boneTransforms[i] = m_bindPose[i];
                else
                {
                    var tmp = ((_allblend <= 1.0f) ? 1.0f : _allblend);

                    for (int b = 0; b < _blend.Count; ++b)
                    {
                        KeyValuePair<float, CTransform> _kvp = _blend[b];
                        float factor = (_kvp.Key / tmp);
                        _t.Position += _kvp.Value.Position * factor;
                        _t.Rotation += _kvp.Value.Rotation * factor;
                        _t.Scale += _kvp.Value.Scale * factor;
                    }

                    if (_allblend < 1.0f)
                    {
                        CTransform bindpose = m_hierarchy[i].BindPose;
                        float elapseFactor = 1.0f - _allblend;
                        _t.Position += bindpose.Position * elapseFactor;
                        _t.Rotation += bindpose.Rotation * elapseFactor;
                        _t.Scale += bindpose.Scale * elapseFactor;
                    }

                    _t.Rotation = Quaternion.Normalize(_t.Rotation);

                    m_boneTransforms[i] = _t.Transform;
                }
                _blend.Clear();
            }

            SetBindPose(m_boneTransforms);

            HierachiTransfom(world);

            foreach (var el in m_attachs)
                el.Update(this);

            if (m_ending.Count > 0)
            {
                foreach (var elem in m_ending)
                    m_config.Remove(elem);
                m_ending.Clear();
            }
        }

        public void AddBlender(IBlendController bc)
        {
            m_blenders.Insert(bc, (e) =>
            {
                return e.Priority > bc.Priority;
            });
            bc.BlendingEnded += new BlendEvent(bc_BlendingEnded);
        }

        private void bc_BlendingEnded(IBlendController sender)
        {
            sender.BlendingEnded -= bc_BlendingEnded;
            m_blenders.Remove(sender);
        }

        protected virtual void UpdateOverride(Matrix world)
        {

        }

        public bool SetBindPose(Matrix[] pose)
        {
            if (pose.Length == this.m_bindPose.Length)
            {
                this.m_bindPose = pose;
                return true;
            }
            return false;
        }

        public void ResetBindPose()
        {
            for (int i = 0; i < m_nodeCount; ++i)
            {
                m_bindPose[i] = m_hierarchy[i].BindPose.Transform;
            }
        }

        public CAttach AttachNodeTo(CNode _node, string _toName)
        {
            var _noded = (from el in m_attachs where el.Node == _node select el).SingleOrDefault();
            var indexs = (from el in m_hierarchy.Nodes where el.Name == _toName select el);
            Debug.Assert(indexs.Count() == 1, "Name Not Found");

            var _n = indexs.ElementAt(0);

            CAttach _attach = null;

            if (_noded != null)
            //{
                Detach(_noded);
            //    _noded.Reset(_n.Index, _node, _n.IsBones);
            //    _attach = _noded;
            //}
            //else
            _attach = new CAttach(_n.Index, _node);//((_n.IsBones) ? m_skinindexer[_n.Index] : _n.Index)
            m_attachs.Add(_attach);
            return _attach;
        }

        public CNode Detach(CNode _node)
        {
            var _attach = (from el in m_attachs where el.Node == _node select el).SingleOrDefault();
            if (_attach == null)
                return _node;
            m_attachs.Remove(_attach);
            return _attach.Detach();
        }

        public CNode Detach(CAttach _node)
        {
            return Detach(_node.Node);
        }

        public void DetachAll()
        {
            var _attachs = m_attachs.ToArray();
            foreach (var el in _attachs)
                Detach(el);
        }

        protected virtual void HierachiTransfom(Matrix world)
        {
            m_worldTransforms[0] = m_boneTransforms[0] * world;

            // Child bones.
            for (int node = 1; node < m_worldTransforms.Length; node++)
            {
                int parentBone = this.m_hierarchy[node].ParentIndex;

                m_worldTransforms[node] = m_boneTransforms[node] * m_worldTransforms[parentBone];
        }

            if (this.AsSkinnedSkeleton)
            {
                for (int e = 0; e < m_skinindexer.Length; ++e)
                {
                    m_skinTransforms[e] = this.m_hierarchy[m_skinindexer[e]].InvBindPose * m_worldTransforms[m_skinindexer[e]];
                }
            }
        }

        private void conf_AnimationEnded(CAnimConfig sender)
        {
            sender.AnimationEnded -= conf_AnimationEnded;
            m_animEnded.Add(sender.AnimName, sender);
            //m_config.Remove(sender);
            m_ending.Add(sender);
        }

        public bool IsPlaying(string name)
        {
            return (from el in m_config where el.AnimName == name select el).SingleOrDefault() != null;
        }

        public bool IsPlaying(CAnimConfig _config)
        {
            return (from el in m_config where el == _config select el).SingleOrDefault() != null;
        }

        public CAnimConfig GetAnimConfig(string animationName)
        {
            return (from el in m_config where el.AnimName == animationName select el).SingleOrDefault();
        }        
        
        #endregion
    }
}
